package
{
	import com.adobe.utils.AGALMiniAssembler;
	import com.adobe.utils.PerspectiveMatrix3D;
	import com.bit101.components.CheckBox;
	import com.bit101.components.PushButton;
	import com.bit101.components.Text;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.getTimer;
	
	import fps.Debug;
	
	import utils.Entity3D;
	import utils.EntityAbsolute3D;
	import utils.parser.Stage3D_md5anim_parser;
	import utils.parser.Stage3D_md5mesh_parser;
	import utils.parser.Stage3D_obj_parser;
	import utils.parser.Stage3d_md5_parser;
	
	[SWF(frameRate="60")]
	public class ModelLookAs3 extends Sprite
	{
		
		private var context3D:Context3D;
		private var entity:EntityAbsolute3D;
		private var shaderProgram:Program3D;
		private var texture:Texture;
		
		//[Embed(source="assets/doom3/archvile2.jpg")]
		[Embed(source="assets/hellknight/hellknight.jpg")]
		private var defaultCls:Class;
		
		private var projectionmatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D();
		private var viewmatrix:Matrix3D = new Matrix3D;
		
		public function ModelLookAs3()
		{
			if(stage){
				addToStage();
			}else{
				this.addEventListener(Event.ADDED_TO_STAGE,addToStage);
			}
			
		}
		
		private function addToStage(event:Event=null):void{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
			
			stage.addChild(new Debug());
			addView()
		}
		
		private function onContext3DCreate(event:Event):void
		{
			var t:Stage3D = event.target as Stage3D;
			context3D = t.context3D;
			if (context3D == null)
			{
				// Currently no 3d context is available (error!)
				trace('ERROR: no context3D - video driver problem?');
				return;
			}
			trace(context3D.driverInfo);
			context3D.enableErrorChecking = true;
			//if(stage.width == 0){
			context3D.configureBackBuffer(stage.stageWidth,stage.stageHeight, 2, true);
			//}else{
			//	context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, true);
			//}
			initTexture(Bitmap(new defaultCls()).bitmapData);
			initShaders();
			
			projectionmatrix.identity();
			// 45 degrees FOV, 640/480 aspect ratio, 0.1=near, 150000=far
			projectionmatrix.perspectiveFieldOfViewRH(
				45, stage.stageWidth / stage.stageHeight, 0.01, 150000.0);
			
			this.addEventListener(Event.ENTER_FRAME,renderScene);
			initEntity();
			initControl();
		}
		
		
		private function initEntity():void{
			var urlloader:URLLoader = new URLLoader();
			urlloader.addEventListener(Event.COMPLETE,onLoadCom);
			//urlloader.load(new URLRequest("assets/art/box.obj"));
			//urlloader.load(new URLRequest("assets/hellknight/hellknight.md5mesh"));
			//urlloader.load(new URLRequest("assets/doom3/archvile.md5mesh"));
			urlloader.load(new URLRequest("assets/doom3/pan.md5mesh"));
		}
		private var mesh:Stage3D_md5mesh_parser;
		private var anim:Stage3D_md5anim_parser;
		private function onLoadCom(event:Event):void{
			var str:String = event.target.data;
			/* var md5:Stage3D_md5mesh_parser = new Stage3D_md5mesh_parser(str,this.context3D)
			return;
			var p:Stage3D_obj_parser = new Stage3D_obj_parser(str,this.context3D); */
			//entity = new EntityAbsolute3D(str,this.context3D,shaderProgram,texture);
			mesh = new Stage3D_md5mesh_parser(str,this.context3D)
			//var p:Stage3D_md5anim_parser = new Stage3D_md5anim_parser(str);
			//return	
			var urlloader:URLLoader = new URLLoader();
			urlloader.addEventListener(Event.COMPLETE,onLoadComAnim);
			//urlloader.load(new URLRequest("assets/hellknight/stand.md5anim"));
			//urlloader.load(new URLRequest("assets/doom3/stand.md5anim"));
			urlloader.load(new URLRequest("assets/doom3/pan.md5anim"));
		}
		private var allmesh:Stage3d_md5_parser;
		private function onLoadComAnim(event:Event):void{
			var str:String = event.target.data;
			anim = new Stage3D_md5anim_parser(str);
			//anim.setBaseInfo(mesh);
			allmesh = new Stage3d_md5_parser(mesh,anim,context3D);
			entity = new EntityAbsolute3D(null,this.context3D,shaderProgram,texture);
			entity.mesh = allmesh;
		}
		
		private function initControl():void{
			stage.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL,onMousewheel);
			//stage.addEventListener(MouseEvent.RIGHT_CLICK,onRightClick);
			//stage.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN,onRightMouseDown);
			//stage.addEventListener(MouseEvent.RIGHT_MOUSE_UP,onRightMouseUp);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,onKey);
		}
		private var flag:String;
		private var beginPointX:Number = 0;
		private var rotationDistanceX:Number = 0;
		
		private var beginPointY:Number = 0;
		private var rotationDistanceY:Number = 0;
		
		private var scaleXYZ:Number = 1;
		private var reduceX:Number = 0;
		private var reduceY:Number = 0;
		private function onMouseDown(event:MouseEvent):void{
			beginPointX = event.stageX;
			beginPointY = event.stageY;
			stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
			flag = "left";
		}
		private function onMouseMove(event:MouseEvent):void{
			var mousex:Number = event.stageX;
			var mousey:Number = event.stageY;
			if(flag == "left"){
				rotationDistanceX = mousex - beginPointX;
				beginPointX = mousex;
				rotationDistanceY = mousey - beginPointY;
				beginPointY = mousey;
			}else if(flag == "right"){
				rotationDistanceRightX = mousex - beginPointRightX;
				beginPointRightX = mousex;
				viewmatrix.appendRotation(rotationDistanceRightX/3,Vector3D.Y_AXIS);
				rotationDistanceRightY = mousey - beginPointRightY;
				beginPointRightY = mousey;
				viewmatrix.appendRotation(rotationDistanceRightY/3,Vector3D.X_AXIS);
			}
			
		}
		private function onMouseUp(event:MouseEvent):void{
			stage.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
			if(rotationDistanceX != 0)
				reduceX = -rotationDistanceX/Math.abs(rotationDistanceX)/40;
			if(rotationDistanceY != 0)
				reduceY = -rotationDistanceY/Math.abs(rotationDistanceY)/40;
		}
		
		private var beginPointRightX:Number = 0;
		private var rotationDistanceRightX:Number = 0;
		
		private var beginPointRightY:Number = 0;
		private var rotationDistanceRightY:Number = 0;
		
		private function onRightMouseDown(event:MouseEvent):void{
			beginPointRightX = event.stageX;
			beginPointRightY = event.stageY;
			stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
			flag = "right";
		}
		
		private function onRightMouseUp(event:MouseEvent):void{
			stage.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
		}
		
		
		private function onMousewheel(event:MouseEvent):void{
			this.scaleXYZ += event.delta/200;
		}
		
		
		private function onRightClick(event:MouseEvent):void{
			rotationDistanceX = 0;
			rotationDistanceY = 0;
		}
		private function initShaders():void
		{
			// A simple vertex shader which does a 3D transformation
			// for simplicity, it is used by all four shaders
			var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble
				(
					Context3DProgramType.VERTEX,
					// 4x4 matrix multiply to get camera angle
					"m44 op, va0, vc0\n" +
					// tell fragment shader about XYZ
					"mov v0, va0\n" +
					// tell fragment shader about UV
					"mov v1, va1\n"// +
					// tell fragment shader about RGBA
					//"mov v2, va2"
				);
			// textured using UV coordinates
			var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					// and store the interpolated value in ft0
					"tex ft0, v1, fs0 <2d,linear,repeat,miplinear>\n"+
					// move this value to the output color
					"mov oc, ft0\n"
				);
			// combine shaders into a program which we then upload to the GPU
			shaderProgram = context3D.createProgram();
			shaderProgram.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler.agalcode);
		}
		
		private function initTexture(bitmapdata:BitmapData):void{
			texture = this.context3D.createTexture(bitmapdata.width,bitmapdata.height,Context3DTextureFormat.BGRA,false);
			uploadTextureWithMipmaps(texture,bitmapdata);
		}
		
		public function uploadTextureWithMipmaps(dest:Texture, src:BitmapData):void
		{
			var ws:int = src.width;
			var hs:int = src.height;
			var level:int = 0;
			var tmp:BitmapData;
			var transform:Matrix = new Matrix();
			
			tmp = new BitmapData( src.width, src.height, true, 0x00000000);
			
			while ( ws >= 1 && hs >= 1 )
			{                                
				tmp.draw(src, transform, null, null, null, true);    
				dest.uploadFromBitmapData(tmp, level);
				transform.scale(0.5, 0.5);
				level++;
				ws >>= 1;
				hs >>= 1;
				if (hs && ws) 
				{
					tmp.dispose();
					tmp = new BitmapData(ws, hs, true, 0x00000000);
				}
			}
			tmp.dispose();
		}
		private function renderScene(event:Event=null):void{
			//viewmatrix.identity();
			this.context3D.clear(0.5,0.5,0.5);
			if(entity){
				entity.z = -200;
				entity.absoluteRotationY = rotationDistanceX;
				entity.absoluteRotationX = rotationDistanceY;
				entity.scaleX = entity.scaleY = entity.scaleZ = this.scaleXYZ;
				entity.render(viewmatrix,projectionmatrix);
				if(Math.abs(rotationDistanceX) < 1){
					rotationDistanceX = 0;
				}else{
					rotationDistanceX += reduceX;
				}
				if(Math.abs(rotationDistanceY) < 1){
					rotationDistanceY = 0;
				}else{
					rotationDistanceY += reduceY;
				}
				/* rotationDistanceX += reduceX;
				rotationDistanceY += reduceY; */
			}
			this.context3D.present();
		}
		
		
		private var te:int = 10;
		protected function RY_clickHandler(event:MouseEvent):void
		{
			//entity.absoluteRotationY = 10;
			viewmatrix.appendRotation(te,Vector3D.Y_AXIS);
			//te += 3;
		}
		
		
		protected function RX_clickHandler(event:MouseEvent):void
		{
			viewmatrix.appendRotation(-te,Vector3D.Y_AXIS);
			//te -= 3;
		}
		private var tz:Number = 3;
		private function onKey(event:KeyboardEvent):void{
			//trace(event.keyCode)
			switch(event.keyCode){
				case 87:
					viewmatrix.appendTranslation(0,0,tz);
					break;
				case 83:
					viewmatrix.appendTranslation(0,0,-tz);
					break;
				case 65:
					viewmatrix.appendTranslation(tz,0,0);
					break;
				case 68:
					viewmatrix.appendTranslation(-tz,0,0);
					break;
				case 32:
					viewmatrix.appendTranslation(0,-tz,0);
					break;
				case 16:
					allmesh.play();
					break;
				
			}
		}
		
		private function playModel(event:MouseEvent):void{
			this.addEventListener(Event.ENTER_FRAME,onPalyframe);
		}
		private var interable:Boolean;
		private var frameNum:int;
		private var currentFrame:int;
		private function onPalyframe(event:Event):void{
			if(interable){
				frameNum++;
				if(frameNum == 4){
					frameNum = 0;
					allmesh.play();
				}
			}else{
				frameNum++;
				if(frameNum == 20){
					frameNum = 0;
					var result:Boolean = allmesh.gotoAndPlay(currentFrame);
					if(!result){
						currentFrame = 0;
					}
					currentFrame += 5;
				}
			}
			//allmesh.play();
		}
		private function stopModel():void{
			this.removeEventListener(Event.ENTER_FRAME,onPalyframe);
		}
		private function clickInter(event:MouseEvent):void{
			interable = cb.selected;
			frameNum = 0;
		}
		/**
		 * view
		 * */
		
		private var playBtn:PushButton;
		private var cb:CheckBox;
		private var txtInput:Text;
		private function addView():void{
			playBtn = new PushButton(stage,100,0,"播放",playModel);
			cb = new CheckBox(stage,250,0,"插值",clickInter);
			var sp:Shape = new Shape;
			sp.graphics.beginFill(0xff0000,0.5);
			sp.graphics.drawRect(0,0,800,40);
			sp.graphics.endFill();
			this.addChildAt(sp,0);
			
			txtInput = new Text();
			txtInput.x = 300;
			txtInput.height = 20;
			txtInput.addEventListener(Event.CHANGE,onTxt);
			
			this.addChild(txtInput);
		}
		private function onTxt(event:Event):void{
			entity.maxMesh = int(txtInput.text);
		}
		
	}
}